﻿using System;
using System.Threading;

namespace PS3Helpers.Helpers
{
    public class ThreadBase
    {
        private bool fTerminated;
        private readonly Thread fThread;
        private Exception _resultingException;

        public ThreadBase()
        {
            this.fThread = new Thread(new ThreadStart(DoExecute));
        }

        ~ThreadBase()
        {
            Terminate();
        }

        public delegate void ThreadEndedHandler(ThreadBase sender, Exception endexception);
        public event ThreadEndedHandler ThreadEnded;

        public bool Alive
        {
            get
            {
                Thread t = fThread;
                return t != null && t.IsAlive;
            }
        }

        /** 
         * Returns true if and only if Terminate() has been called. 
         */
        public bool Terminated 
        { 
            get { return fTerminated; } 
        }

        public int ManagedThreadID
        {
            get { return fThread.ManagedThreadId; }
        }

        public bool IsBackground
        {
            get { return fThread.IsBackground; }
            set { fThread.IsBackground = value; }
        }

        public string Name
        {
            get { return fThread.Name; }
        }

        public Exception ResultingException
        {
            get { return _resultingException; }
        }

        public ThreadPriority Priority
        {
            get { return fThread.Priority; }
            set { fThread.Priority = value; }
        }

        public virtual void Start()
        {
            if (Alive)
                throw new InvalidOperationException("Cannot call start() on a thread when it's already running!");

            fThread.Start();
        }

        /**
         * Request the subclassed thread execution to terminate in the 
         * softest possible way. That is, setting a termination request 
         * flag only.
         */
        public void Terminate()
        {
            fTerminated = true;
        }

        private void DoExecute()
        {
            try
            {
                Execute();

                try
                {
                    ThreadEndedHandler tmp = ThreadEnded;
                    if (tmp != null)
                    {
                        tmp(this, null);
                    }
                }
                catch { } // We can't be sending a ThreadEnded event with an exception generated by the event handler itself...
            }
            catch (Exception e)
            {
                _resultingException = e;

                ThreadEndedHandler tmp = ThreadEnded;
                if (tmp != null)
                {
                    tmp(this, e);
                }
            }
        }

        /**
         * The entry point of the thread, from the subclass point of view.
         * This method is not called at all if a runner delegate was 
         * specified to the XThreadBase constructor.
         */
        protected virtual void Execute()
        {
        }

        public void WaitUntilThreadHasTerminated()
        {
            if (fThread != null)
               fThread.Join();
        }

        public void Interrupt()
        {
            if (fThread != null)
                fThread.Interrupt();
        }

        public void Sleep(int millisecondsTimeout)
        {
            Thread.Sleep(millisecondsTimeout);
        }

        public bool Wait(DateTime until)
        {
            while (DateTime.Now < until)
            {
                if (!Alive) return true;
                Sleep(10);
            }

            // Will only trigger if wait timed out...
            return false;
        }
        
        public void WaitForOtherThread(ThreadBase otherthread)
        {
            if (otherthread == null)
                return;

            while (true)
            {
                if (!otherthread.Alive)
                {
                    // Make sure the exception is not lost
                    if (otherthread.ResultingException != null)
                        throw otherthread.ResultingException;

                    break;
                }

                if (this.fTerminated && !otherthread.fTerminated)
                    otherthread.Terminate();

                Sleep(10);
            }
        }
    }
}
